Using Method References and Constructor References in Java
Course: Core Java Training
Topic: Using Method References and Constructor References
Course Link: Core Java Training – JustAcademy
Demo Link: Register for a Free Course Demo
Website: JustAcademy Official Website
1. Introduction
Method references and constructor references are features introduced in Java 8 that make code shorter, cleaner, and easier to understand. They are closely related to lambda expressions and functional interfaces.
A method reference allows you to refer to an existing method by using its name instead of writing a lambda expression that simply calls that method. A constructor reference allows you to refer to a constructor when an object needs to be created.
These features are commonly used with the Java Collections Framework, streams, sorting, event handling, and functional interfaces such as Consumer, Function, Supplier, and Comparator.
2. Learning Objectives
- Understand method references and constructor references in Java.
- Learn the relationship between lambda expressions and method references.
- Identify the four common forms of method references.
- Use static and instance method references in practical programs.
- Create objects and arrays using constructor references.
- Apply references with collections, streams, and functional interfaces.
- Identify common compilation errors and follow best practices.
3. What Is a Method Reference?
A method reference is a compact way to represent a lambda expression that calls an existing method. Instead of writing the method invocation explicitly inside a lambda expression, you refer to the method using the double-colon operator ::.
General Syntax
ClassName::methodName
objectName::methodName
ClassName::new
The first two forms refer to methods, while ClassName::new refers to a constructor.
Example Without a Method Reference
import java.util.Arrays;
import java.util.List;
public class MethodReferenceExample {
public static void main(String[] args) {
List<String> names =
Arrays.asList("Amit", "Neha", "Rahul");
names.forEach(name -> System.out.println(name));
}
}
In this example, the lambda expression accepts each name and passes it to System.out.println().
Equivalent Example Using a Method Reference
import java.util.Arrays;
import java.util.List;
public class MethodReferenceExample {
public static void main(String[] args) {
List<String> names =
Arrays.asList("Amit", "Neha", "Rahul");
names.forEach(System.out::println);
}
}
Both examples produce the same output. The method reference is shorter because it directly refers to the existing println() method.
4. Understanding the Double-Colon Operator (::)
The double-colon operator is known as the method reference operator. It separates the class or object that provides a method from the method name itself.
| Expression |
Meaning |
Math::abs |
References the static abs() method of the Math class. |
System.out::println |
References the println() method on the existing output object. |
String::toUpperCase |
References an instance method of String that can be applied to a String object. |
ArrayList::new |
References a constructor of ArrayList. |
A method reference does not execute the referenced method immediately by itself. It supplies a reference that can be invoked through a compatible functional interface.
5. Relationship Between Lambda Expressions and Method References
Lambda expressions describe behavior using parameters and an expression or statement block. When a lambda expression only calls an existing method and its parameters match that method's arguments, it may be replaced by a method reference.
Lambda Expression
names.forEach(name -> System.out.println(name));
Method Reference
names.forEach(System.out::println);
| Lambda Expression |
Method Reference |
x -> System.out.println(x) |
System.out::println |
x -> Integer.parseInt(x) |
Integer::parseInt |
() -> new ArrayList<String>() |
ArrayList<String>::new |
s -> s.toUpperCase() |
String::toUpperCase in a compatible context |
Not every lambda expression can be converted into a method reference. A lambda that performs several operations, uses custom logic, or transforms its parameters in a way that does not match an existing method usually needs to remain a lambda expression.
6. Types of Method References
Java commonly categorizes method references into four forms.
| Type |
Syntax |
Example |
| Static method reference |
ClassName::staticMethod |
Integer::parseInt |
| Instance method of a particular object |
objectName::instanceMethod |
System.out::println |
| Instance method of an arbitrary object of a type |
ClassName::instanceMethod |
String::compareToIgnoreCase |
| Constructor reference |
ClassName::new |
ArrayList::new |
7. Static Method References
A static method reference refers to a static method belonging to a class. Static methods can be called using the class name without creating an instance of that class.
Syntax
ClassName::staticMethodName
Example: Converting Strings into Integers
import java.util.Arrays;
import java.util.List;
public class StaticMethodReferenceDemo {
public static void main(String[] args) {
List<String> values =
Arrays.asList("10", "20", "30");
List<Integer> numbers =
values.stream()
.map(Integer::parseInt)
.toList();
System.out.println(numbers);
}
}
Expected output:
[10, 20, 30]
Explanation: The map() operation needs a function that accepts a String and returns an Integer. The static method Integer.parseInt() has a compatible signature, so Integer::parseInt can be used.
Note: The Stream.toList() method is available in Java 16 and later. For Java 8 compatibility, replace the final stream operation with .collect(Collectors.toList()) and import java.util.stream.Collectors.
Another Example: Absolute Values
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class AbsoluteValueDemo {
public static void main(String[] args) {
List<Integer> numbers =
Arrays.asList(-5, -10, 15, -20);
List<Integer> result = numbers.stream()
.map(Math::abs)
.collect(Collectors.toList());
System.out.println(result);
}
}
Expected output:
[5, 10, 15, 20]
8. Instance Method References of a Particular Object
This type refers to an instance method of a specific, already-existing object. The object is written before the double-colon operator.
Syntax
objectName::instanceMethodName
Example: Printing Messages
import java.util.Arrays;
import java.util.List;
public class InstanceMethodReferenceDemo {
public static void main(String[] args) {
List<String> messages =
Arrays.asList("Welcome", "Learn Java", "Practice Daily");
messages.forEach(System.out::println);
}
}
Expected output:
Welcome
Learn Java
Practice Daily
Explanation: System.out is an existing object, and println() is an instance method available on that object. Each list element is passed to the referenced method.
Example: Using a Custom Object
import java.util.function.Supplier;
class GreetingService {
public String getGreeting() {
return "Hello from Java!";
}
}
public class GreetingDemo {
public static void main(String[] args) {
GreetingService service = new GreetingService();
Supplier<String> greeting = service::getGreeting;
System.out.println(greeting.get());
}
}
Expected output:
Hello from Java!
Here, service::getGreeting refers to the getGreeting() method of the particular service object. The Supplier functional interface provides a method called get() that returns the result.
9. Instance Method References of an Arbitrary Object of a Particular Type
This form refers to an instance method through a class or type name rather than a specific object variable. The object on which the method is invoked is supplied as an argument by the functional interface.
Syntax
ClassName::instanceMethodName
Example: Converting Names to Uppercase
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class UppercaseReferenceDemo {
public static void main(String[] args) {
List<String> names =
Arrays.asList("amit", "neha", "rahul");
List<String> uppercaseNames = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
System.out.println(uppercaseNames);
}
}
Expected output:
[AMIT, NEHA, RAHUL]
Explanation: The expression String::toUpperCase can be understood as a function that receives a String object and calls toUpperCase() on that object. It is equivalent to name -> name.toUpperCase().
Example: Sorting Strings Without Case Sensitivity
import java.util.Arrays;
import java.util.List;
public class SortingReferenceDemo {
public static void main(String[] args) {
List<String> names =
Arrays.asList("rahul", "Amit", "neha");
names.sort(String::compareToIgnoreCase);
System.out.println(names);
}
}
Expected output:
[Amit, neha, rahul]
The comparator receives two strings. The method reference uses the first string as the object on which compareToIgnoreCase() is called and passes the second string as its argument.
10. What Is a Constructor Reference?
A constructor reference is a special form of method reference used to refer to a class constructor. It uses the class name followed by the double-colon operator and the keyword new.
Syntax
ClassName::new
Constructor references are useful when a functional interface must create and return a new object. The compiler determines which constructor to use from the target functional interface's parameter and return types.
11. Constructor References with Supplier
The Supplier<T> functional interface represents an operation that takes no arguments and returns a value of type T. It is useful for referring to no-argument constructors.
Example: Creating an ArrayList
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class SupplierConstructorDemo {
public static void main(String[] args) {
Supplier<List<String>> listSupplier =
ArrayList::new;
List<String> names = listSupplier.get();
names.add("Amit");
names.add("Neha");
System.out.println(names);
}
}
Expected output:
[Amit, Neha]
Explanation: The expression ArrayList::new references the no-argument constructor of ArrayList. Calling listSupplier.get() creates a new list.
12. Constructor References with Function
The Function<T, R> functional interface accepts one argument of type T and returns a result of type R. A constructor reference can be assigned to a Function when the constructor accepts one compatible argument and returns the constructed object.
Example: Creating Student Objects
import java.util.function.Function;
class Student {
private String name;
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class StudentConstructorDemo {
public static void main(String[] args) {
Function<String, Student> studentCreator =
Student::new;
Student student = studentCreator.apply("Priya");
System.out.println(student.getName());
}
}
Expected output:
Priya
Explanation: The constructor Student(String name) accepts a String and creates a Student object. Therefore, Student::new can be used as a Function<String, Student>.
13. Constructor References with BiFunction
The BiFunction<T, U, R> interface accepts two arguments and returns a result. It can reference a constructor that accepts two compatible parameters.
Example: Creating a Product
import java.util.function.BiFunction;
class Product {
private String name;
private double price;
public Product(String name, double price) {
this.name = name;
this.price = price;
}
public void display() {
System.out.println("Product: " + name);
System.out.println("Price: " + price);
}
}
public class ProductConstructorDemo {
public static void main(String[] args) {
BiFunction<String, Double, Product> creator =
Product::new;
Product product = creator.apply("Keyboard", 799.0);
product.display();
}
}
Expected output:
Product: Keyboard
Price: 799.0
The constructor receives the product name and price and returns a new Product object. The functional interface's two input types must match the constructor's parameter types.
14. Constructor References for Arrays
Constructor references can also create arrays. The syntax uses an array type followed by ::new. The functional interface supplies the required array length.
Example: Creating a String Array
import java.util.function.IntFunction;
import java.util.Arrays;
public class ArrayConstructorReferenceDemo {
public static void main(String[] args) {
IntFunction<String[]> arrayCreator = String[]::new;
String[] names = arrayCreator.apply(3);
names[0] = "Amit";
names[1] = "Neha";
names[2] = "Rahul";
System.out.println(Arrays.toString(names));
}
}
Expected output:
[Amit, Neha, Rahul]
Explanation: The IntFunction<String[]> accepts an integer and returns a String array. The expression String[]::new creates an array of the requested length.
15. Using Method References with Java Streams
Streams allow developers to process collections through operations such as filter(), map(), sorted(), and forEach(). Method references often make these operations easier to read.
Example: Filtering and Printing Names
import java.util.Arrays;
import java.util.List;
public class StreamMethodReferenceDemo {
public static void main(String[] args) {
List<String> names =
Arrays.asList("Amit", "Ananya", "Rahul", "Arjun");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
}
}
Expected output:
Amit
Ananya
Arjun
The filter operation retains names beginning with A, while the method reference prints each remaining name. The lambda is kept for filtering because it expresses a condition rather than merely calling one existing method.
16. Using Constructor References with Streams
Constructor references are particularly useful when transforming a collection of input values into objects of another type.
Example: Converting Names into Student Objects
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class Learner {
private String name;
public Learner(String name) {
this.name = name;
}
@Override
public String toString() {
return "Learner{name='" + name + "'}";
}
}
public class StreamConstructorDemo {
public static void main(String[] args) {
List<String> names =
Arrays.asList("Amit", "Neha", "Rahul");
List<Learner> learners = names.stream()
.map(Learner::new)
.collect(Collectors.toList());
learners.forEach(System.out::println);
}
}
Expected output:
Learner{name='Amit'}
Learner{name='Neha'}
Learner{name='Rahul'}
The map() operation converts each String into a Learner object. The constructor reference selects the constructor that accepts a String.
17. Method References vs. Constructor References
| Feature |
Method Reference |
Constructor Reference |
| Purpose |
Refers to an existing method. |
Refers to a constructor that creates an object. |
| Syntax |
ClassName::methodName or object::methodName |
ClassName::new |
| Typical result |
Depends on the referenced method. |
Returns a newly created object or array. |
| Common interfaces |
Consumer, Function, Predicate, Comparator |
Supplier, Function, BiFunction, IntFunction |
| Example |
System.out::println |
Student::new |
18. Important Functional Interfaces
| Functional Interface |
Purpose |
Example |
Consumer<T> |
Accepts one input and returns no result. |
System.out::println |
Supplier<T> |
Supplies a value without accepting an input. |
ArrayList::new |
Function<T, R> |
Accepts one input and returns a result. |
Student::new |
BiFunction<T, U, R> |
Accepts two inputs and returns a result. |
Product::new |
Comparator<T> |
Compares two values for ordering. |
String::compareToIgnoreCase |
IntFunction<R> |
Accepts an int and returns a result. |
String[]::new |
19. Method Reference Type Inference
Java determines whether a method reference is valid by examining the target functional interface. The interface describes the expected number of parameters, their types, and the return type. The referenced method or constructor must be compatible with that expected signature.
For example:
Function<String, Integer> converter = Integer::parseInt;
The Function interface expects a String input and an Integer result. The static method Integer.parseInt(String) returns a primitive int, which Java can convert to Integer through boxing.
Another example:
Supplier<StringBuilder> builderSupplier = StringBuilder::new;
The Supplier interface expects no input and a StringBuilder result, so the no-argument constructor is compatible.
20. Common Errors and How to Fix Them
Error 1: Incompatible Parameter Types
Function<String, Integer> converter = Math::abs;
This is invalid because Math.abs() does not accept a String. Use Integer::parseInt for String-to-integer conversion, or convert the value before passing it to Math.abs().
Error 2: No Matching Constructor
Function<String, Product> creator = Product::new;
This works only if Product has a constructor compatible with a single String argument. If the only available constructor requires a String and a double, use a compatible two-argument functional interface such as BiFunction<String, Double, Product>.
Error 3: Referencing an Instance Method Incorrectly
Supplier<String> result = String::toUpperCase;
This is invalid because String::toUpperCase needs a String instance on which to invoke the method. Use a specific String object, such as "hello"::toUpperCase, or use a Function that receives a String and returns the uppercase result.
Error 4: Using a Method Reference for Complex Logic
A method reference cannot replace a lambda expression that performs multiple unrelated operations. Keep a lambda when it improves clarity or when no single existing method matches the required behavior.
Error 5: Using APIs Not Available in Java 8
Some newer Java APIs, such as Stream.toList(), are not available in Java 8. If the project targets Java 8, use collect(Collectors.toList()) where appropriate.
21. Best Practices
- Use method references when a lambda expression simply calls one existing method.
- Use constructor references when an object can be created directly from the inputs supplied by a functional interface.
- Choose the form that makes the code easiest for other developers to understand.
- Check the target functional interface's input and output types when a reference fails to compile.
- Use descriptive class and method names to make references self-explanatory.
- Keep lambda expressions when custom conditions, calculations, or multiple steps are necessary.
- Check the Java version before using APIs in examples or projects.
- Practice combining references with streams and collections to improve familiarity with functional programming.
22. Practical Exercises
Exercise 1: Print a List of Numbers
Create a list containing five integers and print each integer using a method reference.
Hint: Use forEach() with System.out::println.
Exercise 2: Convert Strings to Integers
Create a list of numeric strings and convert them to integers using a stream and a static method reference.
Hint: Use Integer::parseInt with map().
Exercise 3: Create Employee Objects
Create an Employee class with a constructor that accepts an employee name. Use a Function and a constructor reference to create an Employee object from a String.
Exercise 4: Create an Array
Use an IntFunction to create an array of five String elements using an array constructor reference.
Exercise 5: Sort Names
Create a list of names with different capitalization and sort it using a method reference to a suitable comparison method.
23. Mini Project: Student Registration Processor
Objective: Build a small program that converts student names into Student objects, stores the objects in a list, and prints their names using method and constructor references.
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
class StudentRecord {
private final String name;
public StudentRecord(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
public class StudentRegistrationProcessor {
public static void main(String[] args) {
List<String> inputNames =
Arrays.asList("Amit", "Neha", "Rahul");
List<StudentRecord> students = inputNames.stream()
.map(StudentRecord::new)
.collect(Collectors.toList());
students.stream()
.map(StudentRecord::getName)
.forEach(System.out::println);
}
}
Expected output:
Amit
Neha
Rahul
Project explanation:
StudentRecord::new creates a StudentRecord for each name.
StudentRecord::getName retrieves the name from each student object.
System.out::println prints each name.
Collectors.toList() collects the created objects into a list and is compatible with Java 8.
24. Interview Questions and Answers
Q1. What is a method reference in Java?
A method reference is a compact way to refer to an existing method instead of writing a lambda expression that only invokes that method. It uses the :: operator.
Q2. What is a constructor reference?
A constructor reference refers to a constructor that can create an object when invoked through a compatible functional interface. Its syntax is ClassName::new.
Q3. How many common types of method references are there?
There are four common forms: static method references, instance method references of a particular object, instance method references of an arbitrary object of a particular type, and constructor references.
Q4. Can method references be used without functional interfaces?
In typical Java source code, a method reference needs a target functional interface type so the compiler can determine the expected parameters and return type.
Q5. What is the difference between System.out::println and String::toUpperCase?
System.out::println refers to an instance method of a particular output object. String::toUpperCase refers to an instance method that is applied to a String supplied by the functional interface.
Q6. Can constructor references accept arguments?
Yes. The functional interface can provide arguments to the referenced constructor. For example, Function<String, Student> can use Student::new when Student has a compatible String constructor.
Q7. What is the benefit of constructor references?
They reduce repetitive object-creation lambdas and make object transformations in streams and functional programming easier to read.
Q8. Can all lambda expressions be replaced by method references?
No. Only lambda expressions that correspond to an existing method or constructor with a compatible signature can be replaced. Complex logic often requires a lambda expression.
Q9. What is the purpose of Supplier?
Supplier<T> represents an operation that supplies a value of type T without accepting an input. It can be used with no-argument constructor references.
Q10. Why does a method reference sometimes fail to compile?
Common reasons include incompatible parameter types, an incompatible return type, a missing matching constructor, or using an instance method without providing an appropriate object.
25. Quick Revision
- Method references were introduced in Java 8.
- The
:: operator is used to reference methods and constructors.
ClassName::staticMethod references a static method.
objectName::instanceMethod references a method of a particular object.
ClassName::instanceMethod can reference an instance method applied to an object supplied by the functional interface.
ClassName::new references a constructor.
String[]::new can be used to create arrays.
- Method and constructor references must be compatible with their target functional interface.
- They improve readability when they replace simple method-calling lambdas.
- Use lambda expressions when additional logic is required.
26. Conclusion
Method references and constructor references are important Java 8 features that support functional-style programming. They help developers write more concise code while working with functional interfaces, collections, and streams.
By understanding static method references, instance method references, and constructor references, you can simplify common operations such as printing values, converting data, sorting lists, creating objects, and transforming collections. Regular practice with these features will strengthen your Core Java knowledge and prepare you for Java coding exercises and technical interviews.
27. Additional Learning Resources